package coder.nav.service;

import coder.nav.enums.NavIdeLangEnums;
import coder.nav.mapper.NavIdeMapper;
import coder.nav.model.NavIde;
import coder.nav.model.NavMemo;
import coder.nav.model.NavUser;
import coder.nav.model.bo.NavIdeBO;
import coder.nav.model.bo.NavIdeRunBO;
import coder.nav.model.vo.NavIdeRunOutErrVO;
import coder.nav.model.vo.NavIdeRunVO;
import coder.nav.model.vo.NavKeyValueVO;
import coder.nav.sdk.CoderRunAPI;
import coder.nav.utils.JwtUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
public class NavIdeService extends ServiceImpl<NavIdeMapper, NavIde> {

    @Autowired
    private CoderRunAPI coderRunApi;

    @Autowired
    private NavUserService userService;

    @Autowired
    private NavMemoService memoService;

    /**
     * 代码运行
     * @param bo 入参
     */
    public NavIdeRunOutErrVO run(NavIdeRunBO bo) {
        if(StringUtils.isNotBlank(bo.getUsername()) && StringUtils.isNotBlank(bo.getMd5pwd())) {
            NavUser user = userService.getByUsernameAndMd5pwd(bo.getUsername(), bo.getMd5pwd());
            if(Objects.nonNull(user)) {
                return this.run(bo, user.getId());
            }
        }
        return this.run(bo, JwtUtils.getUserId());
    }

    /**
     * 代码运行
     * @param bo 入参
     * @param userId 用户id
     */
    public NavIdeRunOutErrVO run(NavIdeRunBO bo, Long userId) {
        String code = this.buildCode(userId, bo.getLang(), bo.getCode());
        // JavaScript 代码直接执行
        NavIdeLangEnums langEnums = NavIdeLangEnums.getByCode(bo.getLang());
        if(langEnums == NavIdeLangEnums.JAVASCRIPT) {
            ScriptEngineManager manager = new ScriptEngineManager();
            ScriptEngine engine = manager.getEngineByName("JavaScript");
            NavIdeRunOutErrVO outErrVO = new NavIdeRunOutErrVO();
            try {
                String result = String.valueOf(engine.eval(code));
                result = result.replaceAll("</br>", "\n");
                outErrVO.setStdout(result);
            } catch (ScriptException e) {
                outErrVO.setStdout(e.getMessage());
                outErrVO.setStderr(e.getMessage());
            }
            return outErrVO;
        }
        // 使用 Docker 容器运行
        bo.setCode(code);
        NavIdeRunVO vo = coderRunApi.run(bo);
        return vo.getData();
    }

    /**
     * 构建代码
     * @param userId 用户id
     * @param lang 语言
     * @param code 代码
     */
    public String buildCode(Long userId, String lang, String code) {
        // 引入工具类
        List<NavMemo> coderNavPackage = memoService.getCoderNavPackage(lang, userId);
        NavIdeLangEnums langEnums = NavIdeLangEnums.getByCode(lang);
        if(CollectionUtils.isNotEmpty(coderNavPackage)) {
            switch(Objects.requireNonNull(langEnums)) {
                case JAVA:
                    code = code.replaceAll("import java.util.\\*;", "");
                    for(NavMemo item : coderNavPackage) {
                        Pattern pattern = Pattern.compile(item.getTitle());
                        Matcher matcher = pattern.matcher(code);
                        if(!matcher.find()) {
                            continue;
                        }
                        String pack = matcher.group();
                        code = code.replaceAll("import.*" + pack + ".*\n", "");
                        String itemContent = item.getContext()
                                .replaceAll("```.*", "")
                                .replaceAll("public class", "class")
                                .trim();
                        code += "\n" + itemContent;
                    }
                    // 处理包引入问题
                    Set<String> packageSet = new HashSet<>();
                    Pattern importPattern = Pattern.compile("import .*");
                    Matcher importMatcher = importPattern.matcher(code);
                    while(importMatcher.find()) {
                        packageSet.add(importMatcher.group());
                    }
                    if(CollectionUtils.isNotEmpty(packageSet)) {
                        for(String packageStr : packageSet) {
                            code = code.replaceAll(packageStr + "\n", "");
                            code = packageStr + "\n" + code;
                        }
                    }
                    code = code.replaceAll("public class.*\\{\n", "public class Main \\{\n")
                            .replaceAll("public static void main\\(.*\\).*", "public static void main(String[] args) throws Exception {");
                    code = "import java.util.*;\n\n" + code;
                    break;
                case PHP:
                    code = "<?php ini_set('date.timezone','Asia/Shanghai');?>\n" + code;
                    for(NavMemo item : coderNavPackage) {
                        Pattern pattern = Pattern.compile(item.getTitle());
                        Matcher matcher = pattern.matcher(code);
                        if (!matcher.find()) {
                            continue;
                        }
                        String itemContent = item.getContext()
                                .replaceAll("```.*", "")
                                .replaceAll("<\\?php", "").replaceAll("\\?>", "")
                                .trim();
                        code = "<?php\n" + itemContent + "?>\n" + code;
                    }
                    break;
                case JAVASCRIPT:
                    for(NavMemo item : coderNavPackage) {
                        Pattern pattern = Pattern.compile(item.getTitle());
                        Matcher matcher = pattern.matcher(code);
                        if (!matcher.find()) {
                            continue;
                        }
                        String itemContent = item.getContext()
                                .replaceAll("```.*", "")
                                .replaceAll("<\\?php", "").replaceAll("\\?>", "")
                                .trim();
                        code += "\n" + itemContent;
                    }
                    code = code.replaceAll("console.log", "printArr.push");
                    code = "var printArr = [];\n\n" + code + "\n\nvar printStr = ''\nfor(var i = 0; i < printArr.length; i++) {printStr += printArr[i] + '</br>';}\nprintStr;";
                    break;
            }
        }
        return code;
    }

    /**
     * 语言
     */
    public List<NavKeyValueVO<String, String>> getLangList() {
        List<NavKeyValueVO<String, String>> list = new ArrayList<>();
        NavIdeLangEnums[] es = NavIdeLangEnums.values();
        for(int i = 0; i < es.length; i++) {
            NavIdeLangEnums e = es[i];
            NavKeyValueVO<String, String> vo = new NavKeyValueVO<>(e.getKey(), e.getIcon() + " " + e.getValue());
            list.add(vo);
        }
        return list;
    }

    /**
     * 代码缓存
     * @param lang 语言
     */
    public String getIde(String lang) {
        // 先获取缓存
        LambdaQueryWrapper<NavIde> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(NavIde::getUserId, JwtUtils.getUserId())
                .eq(NavIde::getLang, lang);
        NavIde cache = this.getOne(queryWrapper);
        if(Objects.nonNull(cache) && StringUtils.isNotBlank(cache.getCode())) {
            return cache.getCode();
        }
        // 缓存没有该用户数据，返回默认模板
        return NavIdeLangEnums.getTemplateByKey(lang);
    }

    /**
     * 保存代码缓存
     * @param bo 入参
     */
    public void saveIde(NavIdeBO bo) {
        LambdaQueryWrapper<NavIde> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(NavIde::getUserId, JwtUtils.getUserId())
                .eq(NavIde::getLang, bo.getLang());
        NavIde entity = this.getOne(queryWrapper);
        if(Objects.isNull(entity)) {
            entity = new NavIde();
            entity.setLang(bo.getLang());
            entity.setCode(bo.getCode());
            entity.setUserId(JwtUtils.getUserId());
            this.save(entity);
            return;
        }
        entity.setCode(bo.getCode());
        this.updateById(entity);
    }

}
